package com.hjw.httpclient.pooldemo;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.pool.PoolStats;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.ArrayList;

/**
 * 创建 HttpClient 连接池对象
 *
 * @author hjwStrart
 * @time 2022-11-22 20:21
 * @version: 1.0
 */
@Slf4j
public class Demo {

    private static CloseableHttpClient closeableHttpClient;

    private static PoolingHttpClientConnectionManager cm;

    static {

        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        /*
         1.绕过不安全的https请求的证书验证
         */
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", trustHttpsCertificates())
                .build();
        /*
        2.创建连接池管理对象
         */
        cm = new PoolingHttpClientConnectionManager(registry);
        cm.setMaxTotal(50); // 设置连接池最大由50个连接(生产环境建议 <= 20)
        /*
        一个 （ip + port） 就是一个路由，只要 ip + port 相等，就是同一个路由
        假设一个 （ip + port）由多个域名，只要这些域名指向同一个 （ip + port）那就是一个路由
         */
        cm.setDefaultMaxPerRoute(50);   // 每个路由默认有多少个连接(生产环境建议 <= 20,如果实在不确定就和最大连接保持一致)
        /*
        3.设置请求默认配置
         */
        ArrayList<Header> defaultHeaders = new ArrayList<>();
        /*
            向 defaultHeaders 中添加默认的请求头信息即可
         */
        // 设置默认的 User-Agent
//        BasicHeader example = new BasicHeader("headerName", "headerValue");
        BasicHeader userAgent = new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36");
        defaultHeaders.add(userAgent);
        httpClientBuilder.setDefaultHeaders(defaultHeaders);
        httpClientBuilder.setConnectionManager(cm);

        // 线程安全，此处初始化一次即可，通过上面的配置
        // 来生成一个用于管理多个连接的连接池CloseableHttpClient
        closeableHttpClient = httpClientBuilder.build();

    }

    /**
     * 创建支持安全协议的连接
     *
     * @return
     * @throws Exception
     */
    private static ConnectionSocketFactory trustHttpsCertificates() {
        SSLContextBuilder builder = new SSLContextBuilder();
        try {
            builder.loadTrustMaterial(null, new TrustStrategy() {
                // 判断是否信任url
                @Override
                public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    return true;
                }
            });
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        SSLContext sslContext = null;
        try {
            sslContext = builder.build();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return new
                SSLConnectionSocketFactory(sslContext,
                new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}
                , null, NoopHostnameVerifier.INSTANCE);
    }

    /**
     * 打印 httpClient连接池 当前状态
     */
    private static void printStat() {
        log.info("当前时间：{}", LocalDateTime.now());
        log.info("连接池最大连接数：{}", cm.getMaxTotal());
        log.info("每个路由的最大连接数：{}", cm.getDefaultMaxPerRoute());
        PoolStats poolStats = cm.getTotalStats();
        log.info("连接池正在被使用的连接数：{}" + poolStats.getLeased());
        log.info("连接池中空闲连接数：{}" + poolStats.getAvailable());
    }
}
